/**
 * @file: util.hpp
 * @author: sdk team
 * @brief
 * @version: 1.0
 * @date 2022-09-29
 *
 * Copyright:
 * © 2018 北京灵汐科技有限公司 版权所有。
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。
 * © 2018 Lynxi Technologies Co., Ltd. All rights reserved.
 * NOTICE: All information contained here is, and remains the property of Lynxi.
 * This file can not be copied or distributed without the permission of Lynxi
 * Technologies Co., Ltd.
 */

#pragma once
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <algorithm>
#include <fstream>
#include <functional>
#include <iostream>
#include <sstream>
#include <vector>
#include "lyn_api.h"

#define CHECK_ERR(err)                                                      \
  do                                                                        \
  {                                                                         \
    auto _err = (err);                                                      \
    if (_err != 0)                                                          \
    {                                                                       \
      std::cout << __FILE__ << ":" << __LINE__ << " " << _err << std::endl; \
      quick_exit(-1);                                                       \
    }                                                                       \
  } while (0)

// 获取当前时间戳(us)
uint64_t getCurrentTimestamp()
{
  struct timespec time_stamp;
  clock_gettime(CLOCK_REALTIME, &time_stamp);
  uint64_t t = time_stamp.tv_sec * 1e6 + time_stamp.tv_nsec / 1e3; // us
  return t;
}

// 获取芯片时间点(返回lynEvent_t)
lynEvent_t getDeviceTimePoint(lynStream_t stream)
{
  lynEvent_t evt = nullptr;
  CHECK_ERR(lynCreateEvent(&evt));
  CHECK_ERR(lynRecordEvent(stream, evt));
  return evt;
}

// 计算两个lynEvent_t间经过时间(ms)
float getEventElapsedTime(lynEvent_t begin, lynEvent_t end)
{
  float elapsedTime = 0;
  CHECK_ERR(lynEventElapsedTime(begin, end, &elapsedTime));
  return elapsedTime;
}

void StreamErrorHandler(lynStream_t stream, lynErrorMsg_t *errorMsg,
                        void *params)
{
  std::cout << std::endl
            << "!!!!!!!!!!!ERROR!!!!!!!!!!!" << std::endl;
  if (errorMsg->errModule == nullptr)
  {
    std::cout << "unknow module : " << errorMsg->errCode << std::endl;
  }
  else
  {
    std::cout << errorMsg->errModule << ": " << errorMsg->errCode << std::endl;
  }
  std::cout << "!!!!!!!!!!!ERROR!!!!!!!!!!!" << std::endl;
  quick_exit(0);
}

void SetCallback(lynStream_t stream, std::function<void()> cb)
{
  CHECK_ERR(lynStreamAddCallback(
      stream,
      [](void *_ud) -> lynError_t
      {
        try
        {
          auto ud = static_cast<std::function<void()> *>(_ud);
          (*ud)();
          delete ud;
        }
        catch (const std::exception &e)
        {
          std::cout << "SetCallback : " << e.what() << std::endl;
          return -1;
        }
        return 0;
      },
      new std::function<void()>(std::move(cb))));
}

/**
 * @brief 验证设备id是否有效
 *
 * @param  iDeviceID 设备id
 * @return true表示有效，false表示无效
 */
bool IsValidDevice(int32_t iDeviceID)
{
  int32_t iDeviceCount = 0;
  CHECK_ERR(lynGetDeviceCount(&iDeviceCount));

  if (iDeviceID >= iDeviceCount)
  {
    return false;
  }

  return true;
}

/**
 * @brief 将内存数据保存到文件
 *
 * @param  buf 内存数据
 * @param  bufSize 内存大小
 * @param  fileName 文件名
 * @return int
 */
int DumpFile(uint8_t *buf, uint32_t bufSize, std::string fileName)
{
  std::ofstream os(fileName);
  if (!os.is_open())
  {
    std::cout << "open err " << fileName << std::endl;
    return -1;
  }
  os.write((char *)buf, bufSize);
  os.close();

  return 0;
}

void ReplacePath(std::string &str, char oldChar, char newChar)
{
  for (size_t i = 0; i < str.length(); ++i)
  {
    if (str[i] == oldChar)
    {
      str[i] = newChar;
    }
  }
}

#define FILE_MAX_SIZE (500 * 1024 * 1024)

/**
 * @brief 检查文件是否存在
 *
 * @param  fileName 文件名
 * @return 存在返回true, 否则返回false
 */
bool IsFileExist(const std::string &filename)
{
  std::ifstream file(filename);
  return (file.good());
}

uint64_t GetFileSize(const std::string &path)
{
  struct stat buf;
  if (stat(path.c_str(), &buf) < 0)
  {
    return 0;
  }
  return (uint64_t)buf.st_size;
}

std::string to_lower(std::string str)
{
  std::transform(std::begin(str), std::end(str), std::begin(str),
                 [](const std::string::value_type &x)
                 {
                   return std::tolower(x, std::locale());
                 });
  return str;
}

enum ShowType
{
  DirectShow,
  SaveFile,
  OnlyApu,
  UnknowType
};

enum ModelType
{
  Yolov5,
  ResNet50,
  UnknownModel
};
class ModelInfo
{
public:
  lynModel_t model = nullptr;
  lynModelDesc_t *modelDesc = nullptr;
  int width, height, batchSize, classNum;
  uint64_t inputSize, outputSize;
  ModelInfo() = default;
  ModelInfo(const char *path) { LoadModelByPath(path); }
  void LoadModelByPath(const char *path, bool hasClassNum = true)
  {
    //
    CHECK_ERR(lynLoadModel(path, &model));
    CHECK_ERR(lynModelGetDesc(model, &modelDesc));

    // 模型宽高与类别数
    width = modelDesc->inputTensorAttrArray->dims[2];
    height = modelDesc->inputTensorAttrArray->dims[1];
    if (hasClassNum)
      classNum = modelDesc->outputTensorAttrArray->dims[modelDesc->outputTensorAttrArray->dimCount - 1] - 5;
    else
      classNum = 0;
    // ipe等输出大小
    batchSize = modelDesc->inputTensorAttrArray->batchSize;
    CHECK_ERR(lynModelGetInputDataTotalLen(model, &inputSize));
    CHECK_ERR(lynModelGetOutputDataTotalLen(model, &outputSize));
  }

  void UnLoadModel()
  {
    CHECK_ERR(lynUnloadModel(model));
    model = nullptr;
    modelDesc = nullptr;
  }
};

std::vector<std::string> GetChildFilePath(const std::string strDir)
{
  std::vector<std::string> files; // 存放文件名

  struct stat st;
  if (stat(strDir.c_str(), &st) == -1)
  {
    return files;
  }

  bool isFile = S_ISREG(st.st_mode);
  if (isFile)
  {
    files.push_back(strDir);
    return files;
  }

  DIR *dir;
  struct dirent *ptr;

  if ((dir = opendir(strDir.c_str())) == NULL)
  {
    std::cout << "Open dir error, may not exist";
  }

  while ((ptr = readdir(dir)) != NULL)
  {
    if (strcmp(ptr->d_name, ".") == 0 ||
        strcmp(ptr->d_name, "..") == 0) /// current dir OR parrent dir
      continue;

    std::string file = strDir + "/" + ptr->d_name;
    struct stat st;
    if (stat(file.c_str(), &st) == -1)
      continue;
    bool isFile = S_ISREG(st.st_mode);
    if (isFile)
    {
      files.push_back(file);
    }
    else
    {
      std::cout << "file mode is :0x%x" << st.st_mode;
    }
  }
  closedir(dir);
  return files;
}
namespace ArgsParse
{
  static std::string argModelPath;      // 模型路径
  static std::string argInputFilePath;  // 待处理的文件
  static std::string argPostPluginPath; // 后处理插件库的路径
  static std::string argOsdPluginPath;  // osd插件库的路径
  static std::uint32_t argChannel = 1;  // 期望在每颗芯片上运行几个通道
  static std::vector<int> argChipsVec;  // 期望在哪些芯片上运行
  static ShowType argShowType = ShowType::DirectShow;

  bool checkChips(std::vector<int> &chipsVec)
  {
    int32_t iDeviceCount = 0;
    CHECK_ERR(lynGetDeviceCount(&iDeviceCount));
    int iDeviceMax = *std::max_element(chipsVec.begin(), chipsVec.end());
    int iDeviceMin = *std::min_element(chipsVec.begin(), chipsVec.end());
    if (iDeviceMin < 0 || iDeviceMax >= iDeviceCount)
    {
      std::cout << " device number is out of range" << std::endl
                << " support set devices 0 - " << iDeviceCount - 1 << std::endl
                << " You set devices " << iDeviceMin << " - " << iDeviceMax
                << std::endl;
      return false;
    }
    return true;
  }

  bool checkChip(int device)
  {
    int32_t iDeviceCount = 0;
    CHECK_ERR(lynGetDeviceCount(&iDeviceCount));
    if (device < 0 || device >= iDeviceCount)
    {
      std::cout << " device number is out of range" << std::endl
                << " support set devices 0 - " << iDeviceCount - 1 << std::endl
                << " You set device " << device << std::endl;
      return false;
    }
    return true;
  }

  bool setChips(const char *str, char delim)
  {
    std::stringstream sStream(str);
    std::string token;
    argChipsVec.clear();
    try
    {
      while (getline(sStream, token, delim))
      {
        argChipsVec.emplace_back(stoi(token));
      }
    }
    catch (...)
    {
      std::cout << " ERROR! " << token << " is not number!" << std::endl;
      return false;
    }
    if (checkChips(argChipsVec) == false)
    {
      return false;
    }
    return true;
  }

  void setShowType(int type)
  {
    switch (type)
    {
    case 0:
      argShowType = ShowType::DirectShow;
      break;

    case 1:
      argShowType = ShowType::SaveFile;
      break;

    case 2:
      argShowType = ShowType::OnlyApu;
      break;

    default:
      std::cout << " You set " << type << ". Using default 0" << std::endl;
      argShowType = ShowType::DirectShow;
      break;
    }
  }

} // namespace ArgsParse